package ph.com.gs3.formalistics.model.ldao;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;

import ph.com.gs3.formalistics.global.constants.UserUpdateOptions;
import ph.com.gs3.formalistics.global.utilities.logging.FLLogger;
import ph.com.gs3.formalistics.global.utilities.logging.FLLogger.LogType;
import ph.com.gs3.formalistics.model.DatabaseHelper;
import ph.com.gs3.formalistics.model.LocalDataAccessObject;
import ph.com.gs3.formalistics.model.tables.UsersTable;
import ph.com.gs3.formalistics.model.vo.business.User;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;

public class UsersLDAO extends LocalDataAccessObject {

	public static final String TAG = UsersLDAO.class.getSimpleName();

	// ============================================================================
	// {{ Local Enumerations

	/**
	 * Represents (last) update fields used for determining when's the last time the
	 * forms, documents, or comments are updated for this user.
	 */
	public static enum UpdateField {
		ALL_FORMS, ALL_DOCUMENTS, ALL_COMMENTS
	}

	// }}

	public UsersLDAO(Context context) {
		super(context);
	}

	public UsersLDAO(DatabaseHelper databaseHelper) {
		super(databaseHelper);
	}

	// ============================================================================
	// {{ Update & Insert Methods

	public User saveUser(User user) {

		ContentValues cv = createCVFromUser(user);

		int insertId = (int) database.insert(UsersTable.NAME, null, cv);

		// log any possible errors
		if (insertId == -1) {	// if no data is inserted
			FLLogger.log(
			        TAG,
			        LogType.ERROR,
			        "No user data was inserted to database upon registration. User data is: "
			                + cv.toString());
		}

		return getUserWithId(insertId);

	}

	public void updateUser(int dbId, User newUserData, EnumSet<UserUpdateOptions> updateOptions)
	        throws UpdateFailedException {

		ContentValues cv = createCVFromUser(newUserData);

		if (updateOptions.contains(UserUpdateOptions.UPDATE_EXCEPT_PASSWORD)) {
			cv.remove(UsersTable.COL_PASSWORD);
		}

		if (updateOptions.contains(UserUpdateOptions.UPDATE_EXCEPT_IS_ACTIVE)) {
			cv.remove(UsersTable.COL_IS_ACTIVE);
		}

		String whereClause = UsersTable.COL_ID + " = ?";
		String[] whereArgs = { Integer.toString(dbId) };

		int affectedRows = database.update(UsersTable.NAME, cv, whereClause, whereArgs);

		if (affectedRows <= 0) {
			throw new UpdateFailedException("Failed updating user with id " + dbId
			        + ". No records updated");
		}

		if (affectedRows > 1) {
			FLLogger.w(TAG,
			        "There are more than one user records affected by the recent update done to user with id "
			                + dbId);
		}

	}

	public void updateUser(int dbId, User newUserData) throws UpdateFailedException {
		updateUser(dbId, newUserData, EnumSet.of(UserUpdateOptions.UPDATE_ALL));
	}

	public void setFormsLastUpdate(int dbId, String lastUpdateDateString)
	        throws InsertFailedException {

		ContentValues cv = new ContentValues();
		cv.put(UsersTable.COL_FORMS_LAST_UPDATE_DATE, lastUpdateDateString);

		String whereClause = UsersTable.COL_ID + " = ?";
		String[] whereArgs = { Integer.toString(dbId) };

		int affectedRows = database.update(UsersTable.NAME, cv, whereClause, whereArgs);

		if (affectedRows <= 0) {
			throw new InsertFailedException();
		}

	}

	/**
	 * Marks the specified user as active in the database and deactivates all other users.
	 * 
	 * @param user
	 * @return
	 * @throws UserNotFoundException
	 */
	public void activateUser(int userId) throws RecordNotExistingException {

		ContentValues activeUsersCV = new ContentValues();
		ContentValues inactiveUsersCV = new ContentValues();

		activeUsersCV.put(UsersTable.COL_IS_ACTIVE, User.ACTIVE);
		inactiveUsersCV.put(UsersTable.COL_IS_ACTIVE, User.INACTIVE);

		String whereClause = UsersTable.COL_ID + " = ?";
		String[] whereArgs = { Integer.toString(userId) };

		int affectedRows = database.update(UsersTable.NAME, activeUsersCV, whereClause, whereArgs);

		if (affectedRows <= 0) {
			// Unable to update anything, the record of the user is not found.
			throw new RecordNotExistingException("Failed to activate user with id = " + userId
			        + ", the specified user is not found.");
		} else {
			// Deactivate remaining users
			String deactivateWhereClause = UsersTable.COL_ID + " != ?";
			database.update(UsersTable.NAME, inactiveUsersCV, deactivateWhereClause, whereArgs);
		}

	}

	public void deactivateAllUsers() {

		ContentValues cv = new ContentValues();

		cv.put(UsersTable.COL_IS_ACTIVE, User.INACTIVE);

		int affectedRows = database.update(UsersTable.NAME, cv, null, null);
		if (affectedRows <= 0) {
			FLLogger.w(TAG, "Warning, no user deactivated");
		}
	}

	// }}

	// ============================================================================
	// Query Methods

	/**
	 * Gets the currently active user account saved in this device. If no such use is
	 * found, null is returned.
	 * 
	 * @return a User object containing the data of the active user if it's found,
	 *         otherwise, null.
	 */
	public User getActiveUser() {

		String whereClause = String.format(Locale.ENGLISH, "u.is_active = %d", User.ACTIVE);
		return getUser(whereClause);

	}

	/**
	 * Gets a user by its local database id.
	 * 
	 * @param id
	 *            the id of the user to get.
	 * @return a User object containing the data of the active user if it's found,
	 *         otherwise, null.
	 */
	public User getUserWithId(int id) {

		String whereClause = String.format(Locale.ENGLISH, "u._id = %d", id);
		return getUser(whereClause);

	}

	public User getUserWithWebAndCompanyId(String webId, int companyId) {

		String whereClause = String.format(Locale.ENGLISH, "u.web_id = %s AND u.company_id = %d",
		        webId, companyId);
		return getUser(whereClause);

	}

	public User getUser(String whereClause) {

		// @formatter:off
		String rawQuery = "SELECT "
							+ "u._id, u.web_id, u.email, u.display_name, u.image_url, "
							+ "u.user_level_id, u.forms_last_update_date, u.password, u.is_active, "
							+ "c._id AS company_id, c.web_id AS company_web_id, "
							+ "c.name AS company_name, c.server "
						+ "FROM Users u " 
						+ "LEFT JOIN Companies c ON u.company_id = c._id "
						+ "WHERE %s";
		// @formatter:on

		String query = String.format(rawQuery, whereClause);

		Cursor cursor = database.rawQuery(query, null);
		cursor.moveToFirst();

		User user = null;

		if (!cursor.isAfterLast()) {
			user = cursorToUser(cursor);
			user.setCompany(CompaniesLDAO.cursorToCompanyWithAliasedColumns(cursor));
		}

		return user;

	}

	public List<User> getCompanyUsers(int companyId) {
		// @formatter:off
		String query = "SELECT "
							+ "u._id, u.web_id, u.email, u.display_name, u.image_url, "
							+ "u.user_level_id, u.forms_last_update_date, u.password, u.is_active, "
							+ "c._id AS company_id, c.web_id AS company_web_id, "
							+ "c.name AS company_name, c.server "
						+ "FROM Users u " 
						+ "LEFT JOIN Companies c ON u.company_id = c._id "
						+ "WHERE u.company_id = ?";
		// @formatter:on

		List<User> users = new ArrayList<>();

		Cursor cursor = database.rawQuery(query, new String[] { Integer.toString(companyId) });
		cursor.moveToFirst();

		while (!cursor.isAfterLast()) {

			User user = cursorToUser(cursor);
			user.setCompany(CompaniesLDAO.cursorToCompanyWithAliasedColumns(cursor));

			users.add(user);

			cursor.moveToNext();

		}

		return users;
	}

	// }}

	// ============================================================================
	// {{ Parser Methods

	/**
	 * Creates a new ContentValues object based on the passed user object. The keys are
	 * based on the columns of the Users table. The created ContentValues object does not
	 * contain the id of the user object, only the web_id so that the ContentValues object
	 * can be ready for insert.
	 * 
	 * @param user
	 *            the user object where the new ContentValues object will be based on.
	 * @return
	 */
	public ContentValues createCVFromUser(User user) {

		ContentValues cv = new ContentValues();

		// Note: the id is intentionally not set, it's auto generated

		cv.put(UsersTable.COL_WEB_ID, user.getWebId());
		cv.put(UsersTable.COL_EMAIL, user.getEmail());
		cv.put(UsersTable.COL_DISPLAY_NAME, user.getDisplayName());

		cv.put(UsersTable.COL_IMAGE_URL, user.getImageURL());
		cv.put(UsersTable.COL_USER_LEVEL_ID, user.getUserLevelId());

		cv.put(UsersTable.COL_FORMS_LAST_UPDATE_DATE, user.getFormsLastUpdateDate());

		cv.put(UsersTable.COL_COMPANY_ID, user.getCompany().getId());

		cv.put(UsersTable.COL_PASSWORD, user.getPassword());
		cv.put(UsersTable.COL_IS_ACTIVE, user.isActive());

		// Note: the update fields are intentionally not set

		return cv;

	}

	private User cursorToUser(Cursor cursor) {

		User user = new User();

		//	@formatter:off
		//	Initialize indices		
		int idIndex 			= cursor.getColumnIndexOrThrow(UsersTable.COL_ID);
			
		int webIdIndex			= cursor.getColumnIndexOrThrow(UsersTable.COL_WEB_ID);
		int emailIndex			= cursor.getColumnIndexOrThrow(UsersTable.COL_EMAIL);
		int displayNameIndex	= cursor.getColumnIndexOrThrow(UsersTable.COL_DISPLAY_NAME);
		int imageURLIndex		= cursor.getColumnIndexOrThrow(UsersTable.COL_IMAGE_URL);
		
		int passwordIndex		= cursor.getColumnIndex(UsersTable.COL_PASSWORD);
		int userLevelIndex		= cursor.getColumnIndexOrThrow(UsersTable.COL_USER_LEVEL_ID);				

		int lastFormsUpdateIndex	= cursor.getColumnIndexOrThrow(UsersTable.COL_FORMS_LAST_UPDATE_DATE);		
		
		//	Initialize values
		int id				= cursor.getInt(idIndex);		
		String webId		= cursor.getString(webIdIndex);
		
		String email		= cursor.getString(emailIndex);
		String displayName 	= cursor.getString(displayNameIndex);
		String imageURL		= cursor.getString(imageURLIndex);

		String userLevelId	= cursor.getString(userLevelIndex);
		
		String lastFormsUpdate = cursor.getString(lastFormsUpdateIndex);
		
		//	@formatter:on

		// Set the user values
		user.setId(id);
		user.setWebId(webId);

		user.setEmail(email);
		user.setDisplayName(displayName);
		user.setImageURL(imageURL);

		user.setUserLevelId(userLevelId);

		user.setFormsLastUpdateDate(lastFormsUpdate);

		if (passwordIndex > 0) {
			String password = cursor.getString(passwordIndex);
			user.setPassword(password);
		}

		return user;

	}

	// }}

}
